package com.chen.script.job;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSONUtil;
import com.chen.base.utils.basic.DateExUtil;
import com.chen.base.utils.file.IoFileUtil;
import com.chen.base.utils.file.WebFileUtil;
import com.chen.root.base.FileChunk;
import com.chen.root.oclass.vo.FileAttrVO;
import com.chen.root.oclass.vo.FileStructureVO;
import com.chen.script.annotation.Job;
import com.chen.script.feign.RemoteFileService;
import lombok.Data;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @Author: 陈锦龙
 * @Date: 2025/3/21 16:23
 */
@Data
@Slf4j
@Component
@ConfigurationProperties(prefix = "file")
@ConditionalOnProperty(value = "file.enable", havingValue = "true")
public class DownloadFileJob extends AbstractJob{

    @Resource
    private RemoteFileService remoteFileService;

    public Integer days;
    public String url;
    public String downParentDir;
    public String targetDir;

    public DownloadFileJob(){
        log.info("初始化 下载文件及子文件 JOB");
    }

    /** 目标目录存储path **/
    private String targetPath() {
        return pathNowDate(targetDir);
    }

    @Job("file")
    public void run() {
        writeDetail("## 开始: {}", DateUtil.now());
        writeDetail("@@ 请求文件结构");
        long start = System.currentTimeMillis();
        String targetPath = targetPath();
        String parentDir = pathNowDate(downParentDir);
        FileStructureVO data = remoteFileService.fileStructure(parentDir, null).getData();

        if (data != null){
            writeDetail("@@ 元数据JSON: {}", JSONUtil.toJsonStr(data));
            writeDetail("@@ 预下载文件数量: {}个", data.getFileAttrs().size());
            // 创建文件夹结构
            mkdir(data);

            int curr = 0;
            for (FileAttrVO fileAttr : data.getFileAttrs()) {
                // 拼接为本地文件路径
                String localFilePath = fileAttr.getFilePath();
                localFilePath = localFilePath.substring(data.getParentDir().length() + 1);
                writeDetail("@@ 进度: {}/{}, {}", ++curr, data.getFileAttrs().size(), localFilePath);
                localFilePath = targetPath + "\\" + localFilePath;

                fileChunkDownload(fileAttr, localFilePath);
            }

        }else {
            writeDetail("@@ 获取文件结构信息失败!!!");
        }

        delBackFileData(days, targetDir.replaceAll("\\{nowdate}", ""));
        writeDetail("@@ 耗时: {}", DateExUtil.format(System.currentTimeMillis() - start));
        writeDetail("## 结束: {}", DateUtil.now());
    }

    /** 文件分块下载 **/
    @SneakyThrows
    private void fileChunkDownload(FileAttrVO fileAttr, String localFilePath) {
        // 计算md5, 如果本地已存在文件, 则无需下载
        String remoteMd5 = remoteFileService.md5(fileAttr.getFilePath(), null).getData();
        fileAttr.setMd5(StrUtil.isNotBlank(remoteMd5) ? remoteMd5 : "/");
        if (exists(localFilePath, remoteMd5)){
            return;
        }

        String addrUrl = url + "/download/chunk";
        int chunkSize = IoFileUtil.DEFAULT_BYTE_SIZE;
        File tempFile = new File(localFilePath + ".temp");
        if (tempFile.exists()){
            FileUtil.del(tempFile);
        }

        Map<String, Object> paramMap = new HashMap<>();
        paramMap.put("filePath", fileAttr.getFilePath());
        paramMap.put("chunkSize", chunkSize);

        // 分块下载数据块
        int currChunk = 1;
        Integer totalChunk = FileChunk.totalChunk(fileAttr.getSize(), chunkSize);
        while (currChunk <= totalChunk){
            paramMap.put("currChunk", currChunk);

            writeDetail(" - 下载数据块: {}/{}", currChunk, totalChunk);
            HttpResponse response = WebFileUtil.createPostResponse(addrUrl, paramMap);
            byte[] bytes = response.bodyBytes();
            IoFileUtil.appendByte(tempFile.getPath(), bytes, bytes.length);

            ++currChunk;
        }

        FileUtil.rename(tempFile, new File(localFilePath).getName(), true);
        // 检查文件md5
        boolean e = fileAttr.getMd5().equals(IoFileUtil.md5(localFilePath));
        if (e){
            writeDetail(" - 合并成功: {}", localFilePath);
        }else {
            File localFile = new File(localFilePath);
            FileUtil.rename(localFile, "[损坏]-" + localFile.getName(), true);
            writeDetail(" - 合并失败: {}", localFilePath);
        }
    }

    /** 创建文件夹结构 **/
    private void mkdir(FileStructureVO data) {
        List<String> dirPathList = data.getDirPathList();
        if (CollectionUtil.isNotEmpty(dirPathList)){
            writeDetail("@@ 创建文件夹结构");
            for (String dirPath : dirPathList) {
                dirPath = dirPath.substring(data.getParentDir().length() + 1);
                dirPath = targetPath() + "\\" + dirPath;
                FileUtil.mkdir(dirPath);
            }
        }
    }

    /** 写入明细 **/
    private void writeDetail(String line, Object... params){
        if (StrUtil.isNotBlank(line)){
            if (line.contains("{}")){
                line = StrUtil.format(line, params);
            }
            log.info(line);

            List<String> list = new ArrayList<>();
            if (line.contains("@") || line.contains("#")){
                list.add(" ");
            }
            list.add(line);

            String txtFilePath = targetPath() + "\\下载明细.txt";
            FileUtil.writeLines(list, txtFilePath, CharsetUtil.CHARSET_UTF_8, true);
        }
    }

    /** 如果已经存在本地文件，则比对MD5，一致则不再需要下载 **/
    private boolean exists(String localFilePath, String remoteMd5){
        File localFile = new File(localFilePath);
        if (localFile.exists()){
            if (IoFileUtil.md5(localFilePath).equals(remoteMd5)){
                writeDetail(" - 文件已存在, 无需下载: {}", localFilePath);
                return true;
            }else {
                FileUtil.del(localFile);
            }
        }

        return false;
    }

    /** 时间占位符 **/
    private String pathNowDate(String dirPath){
        if (dirPath.contains("{nowdate}")){
            dirPath = dirPath.replaceAll("\\{nowdate}", DateUtil.date().toDateStr());
        }

        return dirPath;
    }
}
